/*
Miranda IM for Windows Search

Copyright (C) 2012-2014 Nikolay Raspopov

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include "stdafx.h"
#include "Strings.h"

CStringA UTF8Encode(__in_bcount(nInput) LPCWSTR psInput, __in int nInput)
{
	CStringA strUTF8;
	int nUTF8 = ::WideCharToMultiByte( CP_UTF8, 0, psInput, nInput,
		strUTF8.GetBuffer( nInput * 4 + 1 ), nInput * 4 + 1, NULL, NULL );

	if ( nUTF8 == 0 && GetLastError() == ERROR_INSUFFICIENT_BUFFER )
	{
		nUTF8 = ::WideCharToMultiByte( CP_UTF8, 0, psInput, nInput,
			NULL, 0, NULL, NULL );

		nUTF8 = ::WideCharToMultiByte( CP_UTF8, 0, psInput, nInput,
			strUTF8.GetBuffer( nUTF8 ), nUTF8, NULL, NULL );
	}
	strUTF8.ReleaseBuffer( nUTF8 );

	return strUTF8;
}

CStringA UTF8Encode(__in const CStringW& strInput)
{
	return UTF8Encode( strInput, strInput.GetLength() );
}

CStringW UTF8Decode(__in_bcount(nInput) LPCSTR psInput, __in int nInput)
{
	CStringW strWide;

	// Try UTF-8
	int nWide = ::MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS,
		psInput, nInput, NULL, 0 );
	if ( nWide > 0 )
	{
		nWide = ::MultiByteToWideChar( CP_UTF8, 0, psInput, nInput,
			strWide.GetBuffer( nWide ), nWide );
		strWide.ReleaseBuffer( nWide );
		return strWide;
	}

	// Try ANSI
	nWide = ::MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS,
		psInput, nInput, NULL, 0 );
	if ( nWide > 0 )
	{
		nWide = ::MultiByteToWideChar( CP_ACP, 0, psInput, nInput,
			strWide.GetBuffer( nWide ), nWide );
		strWide.ReleaseBuffer( nWide );
		return strWide;
	}

	// As-is
	return CString( psInput, nInput );
}

CStringW UTF8Decode(__in const CStringA& strInput)
{
	return UTF8Decode( strInput, strInput.GetLength() );
}

// Encodes unsafe characters in a string, turning "hello world" into "hello%20world", for instance
// Takes text and returns a string
CString URLEncode(LPCTSTR pszInputT)
{
	// Setup two strings, one with all the hexidecimal digits, the other with all the characters to find and encode
	static LPCTSTR pszHex	= _T("0123456789ABCDEF");	// A string with all the hexidecimal digits
	static LPCSTR pszUnsafe	= "<>\"#%{}|\\^~[]+?&@=:,$";// A string with all the characters unsafe for a URL

	// The output string starts blank
	CString strOutput;

	// If the input character pointer points to null or points to the null terminator, just return the blank output string
	if ( pszInputT == NULL || *pszInputT == 0 ) return strOutput;

	// Map the wide character string to a new character set
	int nUTF8 = WideCharToMultiByte(
		CP_UTF8,	// Translate using UTF-8, the default encoding for Unicode
		0,			// Must be 0 for UTF-8 encoding
		pszInputT,	// Points to the wide character string to be converted
		-1,			// The string is null terminated
		NULL,		// We just want to find out how long the buffer for the output string needs to be
		0,
		NULL,		// Both must be NULL for UTF-8 encoding
		NULL );

	// If the converted text would take less than 2 bytes, which is 1 character, just return blank
	if ( nUTF8 < 2 ) return strOutput;

	// Make a new array of CHARs which is nUTF8 bytes long
	LPSTR pszUTF8 = new CHAR[ static_cast< UINT>( nUTF8 ) ];

	// Call WideCharToMultiByte again, this time it has the output buffer and will actually do the conversion
	WideCharToMultiByte( CP_UTF8, 0, pszInputT, -1, pszUTF8, nUTF8, NULL, NULL );

	// Set the null terminator in pszUTF8 to right where you think it should be, and point a new character pointer at it
	pszUTF8[ nUTF8 - 1 ] = 0;
	LPCSTR pszInput = pszUTF8;

	// Get the character buffer inside the output string, specifying how much larger to make it
	LPTSTR pszOutput = strOutput.GetBuffer( static_cast< int >( ( nUTF8 - 1 ) * 3 + 1 ) ); // Times 3 in case every character gets encoded

	// Loop for each character of input text
	for ( ; *pszInput ; pszInput++ )
	{
		// If the character code is 32 or less, more than 127, or in the unsafe list
		if ( *pszInput <= 32 || *pszInput > 127 || strchr( pszUnsafe, *pszInput ) != NULL )
		{
			// Write a three letter code for it like %20 in the output text
			*pszOutput++ = _T('%');
			*pszOutput++ = pszHex[ ( *pszInput >> 4 ) & 0x0F ];
			*pszOutput++ = pszHex[ *pszInput & 0x0F ];

		} // The character doesn't need to be encoded
		else
		{
			// Just copy it across
			*pszOutput++ = (TCHAR)*pszInput;
		}
	}

	// Null terminate the output text, and then close our direct manipulation of the string
	*pszOutput = 0;
	strOutput.ReleaseBuffer(); // This closes the string so Windows can again start managing its memory for us

	// Free the memory we allocated with the new keyword above
	delete [] pszUTF8;


	// Return the URL-encoded, %20-filled text
	return strOutput;
}

// Decodes a properly formatted URI, then UTF-8 decodes it
CString URLDecodeANSI(LPCTSTR pszInput)
{
	TCHAR szHex[3] = { 0, 0, 0 };	// A 3 character long array filled with 3 null terminators
	CStringA strOutput;				// The output string, which starts out blank
	int nHex;						// The hex code of the character we found

	int nLength = (int)_tcslen( pszInput );
	LPSTR pszOutput = strOutput.GetBuffer( nLength + 1 );

	// Loop for each character of input text
	for ( ; *pszInput ; pszInput++ )
	{
		// We hit a %, which might be the start of something like %20
		if ( *pszInput == '%' )
		{
			if ( ( szHex[0] = pszInput[1] ) != 0 &&
				 ( szHex[1] = pszInput[2] ) != 0 &&
				 _stscanf_s( szHex, _T("%x"), &nHex ) == 1 &&
				 nHex > 0 )
			{
				*pszOutput++ = (CHAR)nHex;
				pszInput += 2;
			}
			else
			{
				*pszOutput++ = '%';
			}
		} // We hit a +, which is shorthand for space
		else if ( *pszInput == '+' )
		{
			// Add a space to the output text, and move the pointer forward
			*pszOutput++ = ' ';
		}
		else if ( *pszInput == '&' )
		{
			if ( _tcsnicmp( pszInput + 1, _T("amp;"), 4 ) == 0 )
			{
				*pszOutput++ = '&';
				pszInput += 4;
			}
			else if ( _tcsnicmp( pszInput + 1, _T("lt;"), 3 ) == 0 )
			{
				*pszOutput++ = '<';
				pszInput += 3;
			}
			else if ( _tcsnicmp( pszInput + 1, _T("gt;"), 3 ) == 0 )
			{
				*pszOutput++ = '>';
				pszInput += 3;
			}
			else if ( _tcsnicmp( pszInput + 1, _T("quot;"), 5 ) == 0 )
			{
				*pszOutput++ = '\"';
				pszInput += 5;
			}
			else if ( _tcsnicmp( pszInput + 1, _T("apos;"), 5 ) == 0 )
			{
				*pszOutput++ = '\'';
				pszInput += 5;
			}
			else if ( _tcsnicmp( pszInput + 1, _T("nbsp;"), 5 ) == 0 )
			{
				*pszOutput++ = ' ';
				pszInput += 5;
			}
			else if ( pszInput[ 1 ] == '#' &&
				_stscanf_s( &pszInput[ 2 ], _T("%lu;"), &nHex ) == 1 &&
				nHex > 0 )
			{
				*pszOutput++ = (CHAR)nHex;
				while ( *pszInput && *pszInput != ';' ) ++pszInput;
			}
			else
			{
				*pszOutput++ = '&';
			}
		} // The input pointer is just on a normal character
		else
		{
			// Copy it across
			*pszOutput++ = (CHAR)*pszInput;
		}
	}

	*pszOutput = 0;
	strOutput.ReleaseBuffer();
	return UTF8Decode( strOutput );
}

// Decodes encoded characters in a unicode string
CString URLDecodeUnicode(LPCTSTR pszInput)
{
	TCHAR szHex[3] = { 0, 0, 0 };	// A 3 character long array filled with 3 null terminators
	CString strOutput;				// The output string, which starts out blank
	int nHex;						// The hex code of the character we found

	int nLength = (int)_tcslen( pszInput );
	LPTSTR pszOutput = strOutput.GetBuffer( nLength + 1 );

	for ( ; *pszInput ; ++pszInput )
	{
		// We hit a %, which might be the start of something like %20
		if ( *pszInput == '%' )
		{
			if ( ( szHex[0] = pszInput[1] ) != 0 &&
				( szHex[1] = pszInput[2] ) != 0 &&
				_stscanf_s( szHex, _T("%x"), &nHex ) == 1 &&
				nHex > 0 )
			{
				*pszOutput++ = (TCHAR)nHex;
				pszInput += 2;
			}
			else
			{
				*pszOutput++ = '%';
			}
		} // We hit a +, which is shorthand for space
		else if ( *pszInput == '+' )
		{
			// Add a space to the output text, and move the pointer forward
			*pszOutput++ = ' ';
		}
		else if ( *pszInput == '&' )
		{
			if ( _tcsnicmp( pszInput + 1, _T("amp;"), 4 ) == 0 )
			{
				*pszOutput++ = '&';
				pszInput += 4;
			}
			else if ( _tcsnicmp( pszInput + 1, _T("lt;"), 3 ) == 0 )
			{
				*pszOutput++ = '<';
				pszInput += 3;
			}
			else if ( _tcsnicmp( pszInput + 1, _T("gt;"), 3 ) == 0 )
			{
				*pszOutput++ = '>';
				pszInput += 3;
			}
			else if ( _tcsnicmp( pszInput + 1, _T("quot;"), 5 ) == 0 )
			{
				*pszOutput++ = '\"';
				pszInput += 5;
			}
			else if ( _tcsnicmp( pszInput + 1, _T("apos;"), 5 ) == 0 )
			{
				*pszOutput++ = '\'';
				pszInput += 5;
			}
			else if ( _tcsnicmp( pszInput + 1, _T("nbsp;"), 5 ) == 0 )
			{
				*pszOutput++ = ' ';
				pszInput += 5;
			}
			else if ( pszInput[ 1 ] == '#' &&
				_stscanf_s( &pszInput[ 2 ], _T("%lu;"), &nHex ) == 1 &&
				nHex > 0 )
			{
				*pszOutput++ = (TCHAR)nHex;
				while ( *pszInput && *pszInput != ';' ) ++pszInput;
			}
			else
			{
				*pszOutput++ = '&';
			}
		} // The input pointer is just on a normal character
		else
		{
			// Copy it across
			*pszOutput++ = (TCHAR)*pszInput;
		}
	}

	*pszOutput = 0;
	strOutput.ReleaseBuffer();
	return strOutput;
}

// Decodes unsafe characters in a string, turning "hello%20world" into "hello world", for instance
// Takes text and returns a string
CString URLDecode(LPCTSTR pszInput)
{
	LPCTSTR pszLoop = pszInput;
	// Check each character of input text
	for ( ; *pszLoop ; pszLoop++ )
	{
		if ( *pszLoop > 255 )
		{
			// This URI is not properly encoded, and has unicode characters in it. URL-decode only
			return URLDecodeUnicode( pszInput );
		}
	}

	// This is a correctly formatted URI, which must be url-decoded, then UTF-8 decoded.
	return URLDecodeANSI( pszInput );
}

CString URLDecode(__in_bcount(nInput) LPCSTR psInput, __in int nInput)
{
	return URLDecode( (LPCTSTR)UTF8Decode( psInput, nInput ) );
}

CString URLDecode(__in const CStringA& strInput)
{
	return URLDecode( (LPCTSTR)UTF8Decode( strInput ) );
}

CString Escape(const CString& strValue)
{
	bool bChanged = false;

	CString strXML;
	LPTSTR pszXML = strXML.GetBuffer( strValue.GetLength() * 8  + 1 );

	for ( LPCTSTR pszValue = strValue ; *pszValue ; ++pszValue )
	{
		switch ( *pszValue )
		{
		case _T('&'):
			_tcscpy_s( pszXML, 9, _T("&amp;") );
			pszXML += 5;
			bChanged = true;
			break;
		case _T('<'):
			_tcscpy_s( pszXML, 9, _T("&lt;") );
			pszXML += 4;
			bChanged = true;
			break;
		case _T('>'):
			_tcscpy_s( pszXML, 9, _T("&gt;") );
			pszXML += 4;
			bChanged = true;
			break;
		case _T('\"'):
			_tcscpy_s( pszXML, 9, _T("&quot;") );
			pszXML += 6;
			bChanged = true;
			break;
		case _T('\''):
			_tcscpy_s( pszXML, 9, _T("&apos;") );
			pszXML += 6;
			bChanged = true;
			break;
		default:
			if ( *pszValue > 127 )
			{
				pszXML += _stprintf_s( pszXML, 9, _T("&#%lu;"), *pszValue );
				bChanged = true;
			}
			else
				*pszXML++ = *pszValue;
		}
	}

	*pszXML = 0;

	strXML.ReleaseBuffer();

	return bChanged ? strXML : strValue;
}

CString Unescape(LPCTSTR pszXML, int nLength)
{
	CString strValue;

	if ( ! nLength || ! *pszXML )
		return strValue;

	if ( nLength < 0 )
		nLength = (int)_tcslen( pszXML );

	LPTSTR pszValue = strValue.GetBuffer( nLength + 4 );
	LPTSTR pszOut = pszValue;
	LPTSTR pszNull = (LPTSTR)pszXML + nLength;
	TCHAR cNull = *pszNull;
	*pszNull = 0;

	while ( *pszXML && pszXML < pszNull )
	{
		if ( IsSpace( *pszXML ) && *pszXML != 0xa0 )	// Keep non-breaking space
		{
			if ( pszValue != pszOut ) *pszOut++ = ' ';
			pszXML++;
			while ( *pszXML && IsSpace( *pszXML ) && *pszXML != 0xa0 ) pszXML++;
			if ( ! *pszXML || pszXML >= pszNull ) break;
		}

		if ( *pszXML == '&' )
		{
			pszXML++;
			if ( ! *pszXML || pszXML >= pszNull ) break;

			if ( _tcsnicmp( pszXML, _PT("amp;") ) == 0 )
			{
				*pszOut++ = '&';
				pszXML += 4;
			}
			else if ( _tcsnicmp( pszXML, _PT("lt;") ) == 0 )
			{
				*pszOut++ = '<';
				pszXML += 3;
			}
			else if ( _tcsnicmp( pszXML, _PT("gt;") ) == 0 )
			{
				*pszOut++ = '>';
				pszXML += 3;
			}
			else if ( _tcsnicmp( pszXML, _PT("quot;") ) == 0 )
			{
				*pszOut++ = '\"';
				pszXML += 5;
			}
			else if ( _tcsnicmp( pszXML, _PT("apos;") ) == 0 )
			{
				*pszOut++ = '\'';
				pszXML += 5;
			}
			else if ( _tcsnicmp( pszXML, _PT("nbsp;") ) == 0 )
			{
				*pszOut++ = ' ';
				pszXML += 5;
			}
			else if ( *pszXML == '#' )
			{
				int nChar;
				pszXML++;
				if ( ! *pszXML || pszXML >= pszNull || ! _istdigit( *pszXML ) ) break;

				if ( _stscanf_s( pszXML, _T("%lu;"), &nChar ) == 1 )
				{
					*pszOut++ = (TCHAR)nChar;
					while ( *pszXML && *pszXML != ';' ) pszXML++;
					if ( ! *pszXML || pszXML >= pszNull ) break;
					pszXML++;
				}
			}
			else
			{
				*pszOut++ = '&';
			}
		}
		else
		{
			*pszOut++ = *pszXML++;
		}
	}

	*pszNull = cNull;

	strValue.ReleaseBuffer( (int)( pszOut - pszValue ) );

	return strValue;
}
